﻿using System;
using System.Collections.Generic;
using System.Collections.Concurrent;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace Q.Schedulers
{
    public class WorkItemScheduler
    {
        private static TimeSpan s_smallestInterval 
            = TimeSpan.FromMilliseconds(10);
        private static TimeSpan s_shortestLongTermCycle
            = TimeSpan.FromMilliseconds(20);

        private CancellationTokenSource _cts;
        private ConcurrentBag<WorkItemExecutionException> _exceptions;

        private ConcurrentQueue<IExecutableWorkItem> _longTermItems;
        private ConcurrentQueue<IExecutableWorkItem> _midTermItems;
        private ConcurrentQueue<IExecutableWorkItem> _shortTermItems;
        private ConcurrentDictionary<string, IExecutableWorkItem> _workItems;

        private Task _executionTask;
        private Task _schedulerTask;

        private TimeSpan _timeOffset;

        public SchedulerState CurrentState { get; private set; }
        public DateTime CurrentTime { get; private set; }
        public IProducerConsumerCollection<WorkItemExecutionException> 
            Exceptions
        {
            get
            {
                return _exceptions;
            }
        }

        public TimeSpan Interval { get; private set; }
        public TimeSpan LongTermSchedulingCycle { get; private set; }
        public TimeSpan ShortTermSchedulingCycle { get; private set; }

        public WorkItemScheduler()
        {
            _exceptions 
                = new ConcurrentBag<WorkItemExecutionException>();
            _longTermItems 
                = new ConcurrentQueue<IExecutableWorkItem>();
            _midTermItems 
                = new ConcurrentQueue<IExecutableWorkItem>();
            _shortTermItems 
                = new ConcurrentQueue<IExecutableWorkItem>();
            _workItems 
                = new ConcurrentDictionary<string, IExecutableWorkItem>();

            _timeOffset = TimeSpan.Zero;

            CurrentState = SchedulerState.Stopped;
            Interval = TimeSpan.FromMilliseconds(10);
            ShortTermSchedulingCycle = TimeSpan.FromMilliseconds(500);
            LongTermSchedulingCycle = TimeSpan.FromMilliseconds(5000);
        }

        public void CancelWorkItem(string itemId)
        {
            IExecutableWorkItem item = null;
            while (_workItems.ContainsKey(itemId) &&
                !_workItems.TryGetValue(itemId, out item))
            {
                Thread.Sleep(10);
            }

            if (item != null)
            {
                item.IsCancellationRequested = true;  
            }

            while (_workItems.ContainsKey(itemId) &&
                !_workItems.TryUpdate(itemId, item, item))
            {
                Thread.Sleep(10);
            }
        }

        public void ChangeInterval(TimeSpan interval)
        {
            if (interval < s_smallestInterval)
            {
                Interval = s_smallestInterval;
            }
            else
            {
                Interval = interval;
            }
        }

        public void ChangeSchedulingCycle(TimeSpan shortTerm, TimeSpan longTerm)
        {
            if (longTerm <= shortTerm)
            {
                throw new ArgumentException(
                    "Long term cycle must greater than short term cycle.");
            }

            if (shortTerm < s_smallestInterval)
            {
                ShortTermSchedulingCycle = s_smallestInterval;
            }
            else
            {
                ShortTermSchedulingCycle = shortTerm;
            }

            if (longTerm < s_shortestLongTermCycle)
            {
                LongTermSchedulingCycle = s_shortestLongTermCycle;
            }
            else
            {
                LongTermSchedulingCycle = longTerm;
            }
        }

        public bool ScheduleWorkItem(IExecutableWorkItem workItem)
        {
            return true;
        }

        public void Start()
        {
            Start(DateTime.Now);
        }

        public void Start(DateTime baseTime)
        {
            lock (this)
            {
                if (CurrentState == SchedulerState.Stopped)
                {
                    CurrentTime = baseTime;
                    _timeOffset = CurrentTime - DateTime.Now;  

                    _cts = new CancellationTokenSource();
                    _schedulerTask = Task.Factory.StartNew(() =>
                    {

                    }, _cts.Token);
                }
            }
        }

        public void Stop()
        {
            Stop(true);
        }

        public void Stop(bool executeRest)
        {
            lock (this)
            {
                if (CurrentState == SchedulerState.Running)
                {
                    if (executeRest)
                    {
                        CurrentState = SchedulerState.StopPending;
                    }
                    else
                    {
                        CurrentState = SchedulerState.Stopped;
                    }

                    _cts.Cancel();
                } 
            }
        }

        public void UpdateTime()
        {
            CurrentTime = DateTime.Now + _timeOffset;
        }

        private void AddException(WorkItemExecutionException exception)
        {
            if (exception != null)
            {
                while (!Exceptions.TryAdd(exception))
                {
                    Thread.Sleep(10);
                }
            }
        }
        private void ExecuteWorkItem(object state)
        {
            IExecutableWorkItem workItem = (IExecutableWorkItem)state;

            if (workItem != null)
            {
                try
                {
                    workItem.Execute();
                }
                catch (Exception ex)
                {
                    var workItemEx = new WorkItemExecutionException(
                        workItem.Identifier, CurrentTime, String.Empty, ex);

                    AddException(workItemEx);
                }
            }
        }
    }
}
